﻿using Plexi.Configuration;
using Plexi.Tools;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;

namespace Plexi.Science
{
	public enum RockClassifications : byte { Igneous = 0, Sedimentary = 1, Metamorphic = 2, Ice = 3, Unknown = 255 }
	public class RockType
	{
		/// <summary>The probability of the prominent rock in each cell.</summary>
		private const float _prominentRockChance = 0.69f;

		/// <summary>An array of dictionaries; indices correspond to bodies (as per FlightGlobals.Bodies), each body has a dictionary of biome names, and their corresponding array of rock type occurrences.</summary>
		private static Dictionary<string, BiomeRockOccurrences>[] _occurrences;
		private static BiomeRockOccurrences _fallbackOccurrences;
		public static RockType[] AllTypes { get; private set; }
		public static RockType Unknown = new RockType() { Name = "Unknown", Class = RockClassifications.Unknown, SpecificWeight = 2500 };

		public string Name { get; set; }
		public RockClassifications Class { get; set; }
		/// <summary>Kilograms per cubic meter</summary>
		public float SpecificWeight { get; set; }
		public Texture[] Textures { get; set; }
		/// <summary>The index of the type in the list of all types, in order of loading.</summary>
		public int Index { get; set; }
		/// <summary>Returns the string that can be used to name a rock vessel of this type.</summary>
		public string VesselName { get { return Class == RockClassifications.Ice ? "Ice" : "Stone"; } }

		public static int GetRockDensity(int bodyIndex, CBAttributeMapSO.MapAttribute biome)
		{
			BiomeRockOccurrences available = null;
			if (biome != null && _occurrences[bodyIndex].TryGetValue(biome.name, out available) && available != null)
				return available.RockSpawnDensity;
			Log.Add("RockType GetRockDensity: resorting to fallback occurrences because: biome: {0}, body index: {1} ({2})",
				(biome == null ? "null" : biome.name), bodyIndex, FlightGlobals.Bodies[bodyIndex].bodyName);
			return _fallbackOccurrences.RockSpawnDensity;
		}

		public static RockType Choose(int rockTypeSelector, double prominentRockChoice, CBAttributeMapSO.MapAttribute biome, int bodyIndex, RockTypePercent prominent)
		{
			if (prominent != null && prominentRockChoice <= _prominentRockChance)
				return prominent.Type;
			float rockTypeSelectorFloat = (float)rockTypeSelector;
			BiomeRockOccurrences available = null;
			float div = 0;
			if (biome != null && _occurrences[bodyIndex].TryGetValue(biome.name, out available) && available != null)
			{
				if (prominent != null)
					div = (float)prominent.Percent / (float)(available.TypeOccurrences.Length - 1); //if we leave out the prominent, we need to distribute its percentage evenly among all others.
				foreach (RockTypePercent rtp in available.TypeOccurrences)
				{
					if (rtp.Type.Equals(prominent))
						continue;
					if (rockTypeSelectorFloat < rtp.Percent + div)
						return rtp.Type;
					rockTypeSelectorFloat -= rtp.Percent;
				}
			}
			else //no biome or biome not found
				return _fallbackOccurrences.Choose(rockTypeSelector).Type;
			Log.Add("ERROR: RockType random selection: couldn't find a rock type for {0}'s {1}. prominent: {2} ({3}%), rest of avail count: {4}, div: {5}, rockTypeSelectorFloat: {6}, available.TypeOccurrences: {7}",
				FlightGlobals.Bodies[bodyIndex].bodyName,
				biome,
				prominent.Type.Name,
				prominent.Percent,
				((available == null) ? "avail null!" : ((available.TypeOccurrences == null) ? "TypeOccurrences null!" : available.TypeOccurrences.Length.ToString())),
				div,
				rockTypeSelectorFloat,
				((available == null) ? "available null!" :
				(available.TypeOccurrences.Length == 0 ? "none!" :
				(string.Join(" | ", available.TypeOccurrences.Select(to => string.Format("{0} {1}", to.Type.Name, to.Percent)).ToArray()))
				)));
			return Unknown;
		}

		public static RockTypePercent GetProminentTypeForArea(int typeSelector, int bodyIndex, CBAttributeMapSO.MapAttribute biome, double latitude, ref bool cellHasProminentRock)
		{
			int debugTypeSelectorOriginal = typeSelector;
			BiomeRockOccurrences available = null;
			if (_occurrences[bodyIndex] == null)
				throw new ApplicationException(string.Format("SurfaceRocks: ERROR: GetProminentTypeForArea: body {0} doesn't have rock occurrences! At all!", FlightGlobals.Bodies[bodyIndex].bodyName));
			if (biome != null && _occurrences[bodyIndex].TryGetValue(biome.name, out available) && available != null)
			{
				if (biome.Equals("Ice Caps") || biome.Equals("Poles")) //Kerbin: Ice Caps, Duna and Laythe: Poles
				{
					PolarInfo p = Config.PolarCircles.GetPolarInfo(bodyIndex);
					if (p != null && (latitude >= p.ArcticLatitude || latitude <= p.AntarcticLatitude))
					{
						cellHasProminentRock = true;
						return available.TypeOccurrences.First(a => a.Type.Class == RockClassifications.Ice); //TODO later if there are multiple kinds of ice, this needs to be refined.
					}
				}
				if (available.TypeOccurrences == null)
					throw new ApplicationException(string.Format("SurfaceRocks: ERROR: GetProminentTypeForArea: body {0} doesn't have rock occurrences! available.TypeOccurrences == null", FlightGlobals.Bodies[bodyIndex].bodyName));
				return available.Choose(typeSelector);
			}
			else //no biome or biome not found
				return _fallbackOccurrences.Choose(typeSelector);
		}

		public static void LoadFromResourceIfNecessary()
		{
			if (_occurrences != null)
				return;
#if verbose
			Log.Add("Loading rock types from resource...");
#endif
			_occurrences = new Dictionary<string, BiomeRockOccurrences>[FlightGlobals.Bodies.Count];
			string allTypes = Data.RockDataResources.rock_types;
			string[] lines = allTypes.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
			if (lines.Length == 0)
			{
				Log.Add("ERROR: RockType LoadFromResource: no types to load!");
				return;
			}
			char[] lineSplitter = new char[] { ':' }, dataSplitter = new char[] { ',' }, spaceSplitter = new char[] { ' ' };
			string[] typeParts, dataParts, mainParts, rockOccurrences, rockOccurrenceParts, biomes, biomeParts;
			int rockClass;
			float specW;
			List<RockType> allRockTypes = new List<RockType>();
			Texture[] textures = null;
			string name;
			int index = 0;
			foreach (string line in lines)
			{
				if (line.StartsWith("//"))
					continue;
				typeParts = line.Split(lineSplitter, StringSplitOptions.RemoveEmptyEntries);
				if (typeParts.Length != 2)
				{
					Log.Add("ERROR: RockType LoadFromResource failure! typeParts {0}", line);
					return;
				}
				dataParts = typeParts[1].Split(dataSplitter, StringSplitOptions.RemoveEmptyEntries);
				if (dataParts.Length != 2)
				{
					Log.Add("ERROR: RockType LoadFromResource failure! dataParts {0}", typeParts[1]);
					return;
				}
				if (!int.TryParse(dataParts[0].Trim(), out rockClass) ||
					!float.TryParse(dataParts[1].Trim(), out specW))
				{
					Log.Add("ERROR: RockType LoadFromResource format error: rock class: {0} specific weight: {1}", dataParts[0], dataParts[1]);
					return;
				}
				name = typeParts[0].Trim();
				textures = TextureManager.LoadRockTextures(name);
				allRockTypes.Add(new RockType()
				{
					Name = name,
					Class = (RockClassifications)rockClass,
					SpecificWeight = specW,
					Textures = textures,
					Index = index++
				});
#if verbose
				Log.Add("{0}: class: {1}, specific weight: {2}", typeParts[0].Trim(), (RockClassifications)rockClass, specW);
#endif
			}
#if verbose
			Log.Add("{0} rock types loaded.", allRockTypes.Count);
#endif
			AllTypes = allRockTypes.ToArray();
			string alloccurrences = Data.RockDataResources.rock_occurrences;
			lines = alloccurrences.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
			if (lines.Length == 0)
			{
				Log.Add("ERROR: RockType LoadFromResource: no bodies to load!");
				return;
			}
			string bodyName, rockTypeName, biomePart, biomeName;
			int bodyIndex = -1, percent = 0, biomePercent;
			RockType t;
			List<RockTypePercent> rtpsOnBodyBiome = new List<RockTypePercent>();
			List<string> biomeNames = new List<string>();
			RockTypePercent[] resultArr;
			CelestialBody body;
			bool isDefault = false;
#if verbose
			Log.Add("parsing lines:");
#endif
			foreach (string line in lines)
			{
				if (line.StartsWith("//"))
					continue;
				isDefault = line.StartsWith("Default");
#if verbose
				Log.Add("rock_occurrences: processing line: {0}", line);
#endif
				rtpsOnBodyBiome.Clear();
				mainParts = line.Split(lineSplitter, StringSplitOptions.RemoveEmptyEntries);
				if (!isDefault)
				{
					if (mainParts.Length != 2)
					{
						Log.Add("ERROR: RockType LoadFromResource failure! mainParts. Line: {0}", line);
						return;
					}
					bodyName = string.Empty;
					foreach (char c in line.Trim())
					{
						if (c != ' ')
							bodyName += char.ToLower(c);
						else
							break;
					}
#if verbose
				Log.Add("body name: {0}", bodyName);
#endif

					body = FlightGlobals.Bodies.FindByName(bodyName);
					if (body == null)
					{
#if verbose
						Log.Add("RockType LoadFromResource: Couldn't find celestial body with the following name: {0}, ignoring line.", bodyName);
#endif
						continue;
					}
					bodyIndex = body.flightGlobalsIndex;
					biomePart = mainParts[0].Substring(bodyName.Length).Trim();
					if (biomePart.Length == 0)
					{
						Log.Add("ERROR: RockType LoadFromResource failure! biomePart. Line: {0}", line);
						return;
					}
					biomes = biomePart.Split(dataSplitter, StringSplitOptions.RemoveEmptyEntries);
					if (biomes.Length == 0)
					{
						Log.Add("ERROR: RockType LoadFromResource failure: no biomes defined! Line: {0}", line);
						return;
					}
					biomeNames.Clear();
#if verbose
				Log.Add("parsing biomes...");
#endif
					foreach (string biomeInfo in biomes)
					{
#if verbose
					Log.Add("parsing biomeInfo: {0}", biomeInfo);
#endif
						biomeParts = biomeInfo.Split(spaceSplitter, StringSplitOptions.RemoveEmptyEntries);
						biomeName = biomeParts[0].Replace('_', ' ');
						biomePercent = 100;
						if (biomeParts.Length > 2)
						{
							Log.Add("ERROR: RockType LoadFromResource failure! too many biomeParts. Line: {0}", line);
							return;
						}
						biomeNames.Add(biomeName);
						if (biomeParts.Length == 2 && !int.TryParse(biomeParts[1], out biomePercent))
						{
							Log.Add("ERROR: RockType LoadFromResource failure! Could not parse biome percent. Line: {0}", line);
							return;
						}
						if (_occurrences[bodyIndex] == null)
							_occurrences[bodyIndex] = new Dictionary<string, BiomeRockOccurrences>();
						_occurrences[bodyIndex].Add(biomeName, new BiomeRockOccurrences() { RockSpawnDensity = biomePercent });
					}
				}
				rockOccurrences = mainParts[1].Split(dataSplitter, StringSplitOptions.RemoveEmptyEntries);
				int debugPercentSum = 0;
				foreach (string rockOcc in rockOccurrences)
				{
#if verbose
					Log.Add("parsing rock occurrence: {0}", rockOcc);
#endif
					rockOccurrenceParts = rockOcc.Split(spaceSplitter, StringSplitOptions.RemoveEmptyEntries);
					if (rockOccurrenceParts.Length != 2)
					{
						Log.Add("ERROR: RockType LoadFromResource failure! rockOccurrenceParts {0}", rockOcc);
						return;
					}
					rockTypeName = rockOccurrenceParts[0].Trim().ToLower();
					if (!int.TryParse(rockOccurrenceParts[1].Trim(), out percent))
					{
						Log.Add("ERROR: RockType LoadFromResource failure! format error {0}", rockOcc);
						return;
					}
					t = allRockTypes.FirstOrDefault(r => r.Name.ToLower().Equals(rockTypeName));
					if (t == null)
					{
						Log.Add("ERROR: RockType LoadFromResource: there is an occurrence percent defined for a rock type named {0}, but the type is not defined in rock_types.txt.", rockOccurrenceParts[0].Trim());
						continue;
					}
					rtpsOnBodyBiome.Add(new RockTypePercent() { Percent = percent, Type = t });
					debugPercentSum += percent;
				}
				resultArr = rtpsOnBodyBiome.ToArray();
#if verbose
				Log.Add(FlightGlobals.Bodies[bodyIndex].bodyName + " rock occurrences for biomes:");
#endif
				if (!isDefault)
				{
					foreach (string bn in biomeNames)
					{
#if verbose
					Log.Add("   {0}", bn);
#endif
						if (_occurrences[bodyIndex] == null)
						{
							Log.Add("ERROR: body index {0} has no rock occurrences!", bodyIndex);
							return;
						}
						if (_occurrences[bodyIndex][bn] == null)
						{
							Log.Add("ERROR: body index {0} has no rock occurrences for biome name {1}", bodyIndex, bn);
							return;
						}
#if verbose
					Log.Add("Found {0} rock occurrences for {1}'s {2}.", resultArr.Length, FlightGlobals.Bodies[bodyIndex].bodyName, bn);
#endif
						_occurrences[bodyIndex][bn].TypeOccurrences = resultArr;
					}
				}
				else
				{
					_fallbackOccurrences = new BiomeRockOccurrences()
					{
						RockSpawnDensity = 100,
						TypeOccurrences = resultArr
					};
				}
#if verbose
				string debugStr = "      ";
				foreach (var v in resultArr)
					debugStr += string.Format("{0} {1}, ", v.Type.Name, v.Percent);
				Log.Add(debugStr.TrimEnd(' ').TrimEnd(','));
#endif
#if verbose
				Log.Add("Loaded {0} rock occurrences for {1} (total: {2}%)", rtpsOnBodyBiome.Count, FlightGlobals.Bodies[bodyIndex].bodyName, debugPercentSum);
#endif
			}
		}

	}
}
